package org.aurora.client.spring.acquirer;

import org.apache.commons.collections4.MapUtils;
import org.aurora.client.ITaskProcessor;
import org.aurora.client.exception.AuroraTaskRegisterException;
import org.aurora.client.spring.SpringTaskProcessorAcquirer;
import org.aurora.client.spring.annotation.AuroraConfiguration;
import org.aurora.client.spring.annotation.AuroraTask;
import org.aurora.model.entity.ScheduleTaskContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class AuroraTaskAnnotationSpringTaskProcessorAcquirer extends SpringTaskProcessorAcquirer {
    private static final Logger log = LoggerFactory.getLogger(AuroraTaskAnnotationSpringTaskProcessorAcquirer.class);

    public AuroraTaskAnnotationSpringTaskProcessorAcquirer(ApplicationContext applicationContext) {
        super(applicationContext);
    }

    @Override
    public Map<String, ITaskProcessor> acquirer() {
        Map<String, Object> auroraConfigurationBeanMap = this.applicationContext.getBeansWithAnnotation(AuroraConfiguration.class);

        if (MapUtils.isEmpty(auroraConfigurationBeanMap)) {
            return Collections.emptyMap();
        }

        return auroraConfigurationBeanMap
                .values()
                .stream()
                .flatMap(bean -> {
                    Class<?> clazz = bean.getClass();
                    String clazzName = clazz.getName();

                    List<ITaskProcessor> result = new ArrayList<>();
                    // getDeclaredMethods()方法返回当前类声明的所有方法，不考虑这些方法的访问级别，也不包括父类及超类的方法。
                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        if (!method.isAnnotationPresent(AuroraTask.class)) {
                            continue;
                        }

                        AuroraTask auroraTaskAnnotation = method.getAnnotation(AuroraTask.class);
                        String methodName = method.getName();
                        String taskName = auroraTaskAnnotation.value();

                        int modifiers = method.getModifiers();

                        // 判断是否 public 方法，仅 public 方法才会被执行
                        if (!Modifier.isPublic(modifiers)) {
                            log.error("register aurora task by @AuroraTask annotation, " +
                                            "class = {}, method = {}, task name = {},  " +
                                            "method is not public",
                                    clazzName,
                                    methodName,
                                    taskName);
                            throw new AuroraTaskRegisterException(
                                    String.format("register aurora task by @AuroraTask annotation, class = {}, method = {}, task name = {}, method is not public",
                                            clazzName, methodName, taskName
                                    ));
                        }
                        // 参数赋值
                        // 如果有多个上下文参数仅为第一个赋值
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        int taskContextParameterTypeIndex = getTaskContextParameterTypeIndex(parameterTypes);
                        Object[] parameters = new Object[parameterTypes.length - 1];

                        // 新建任务
                        result.add(new ITaskProcessor() {
                            @Override
                            public void processor(ScheduleTaskContext taskContext) throws Exception {
                                if (taskContextParameterTypeIndex != -1) {
                                    parameters[taskContextParameterTypeIndex] = taskContext;
                                }
                                method.invoke(bean, parameters);
                            }

                            @Override
                            public String taskName() {
                                return taskName;
                            }
                        });
                    }

                    return result.stream();
                }).collect(Collectors.toMap(ITaskProcessor::taskName, Function.identity()));
    }

    private int getTaskContextParameterTypeIndex(Class<?>[] parameterTypes) {
        int taskContextIndex = -1;
        for (int i = 0; i < parameterTypes.length; i++) {
            if (parameterTypes[i].equals(ScheduleTaskContext.class)) {
                taskContextIndex = i;
                break;
            }
        }
        return taskContextIndex;
    }
}